package io.kiki.stack.http.feign.apttestgenerator;

import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.EscapingStrategy;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.context.FieldValueResolver;
import com.github.jknack.handlebars.context.JavaBeanValueResolver;
import com.github.jknack.handlebars.context.MapValueResolver;
import com.github.jknack.handlebars.io.URLTemplateSource;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.WildcardType;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
import java.io.IOError;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@SupportedAnnotationTypes({"feign.RequestLine"})
@AutoService(Processor.class)
public class GenerateTestStubAPT extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        System.out.println(annotations);
        System.out.println(roundEnv);

        final Map<TypeElement, List<ExecutableElement>> clientsToGenerate = annotations.stream().map(roundEnv::getElementsAnnotatedWith).flatMap(Set::stream).map(ExecutableElement.class::cast).collect(Collectors.toMap(annotatedMethod -> TypeElement.class.cast(annotatedMethod.getEnclosingElement()), ImmutableList::of, (list1, list2) -> ImmutableList.<ExecutableElement>builder().addAll(list1).addAll(list2).build()));

        System.out.println("Count: " + clientsToGenerate.size());
        System.out.println("clientsToGenerate: " + clientsToGenerate);

        final Handlebars handlebars = new Handlebars();

        final URLTemplateSource source = new URLTemplateSource("stub.mustache", getClass().getResource("/stub.mustache"));
        Template template;
        try {
            template = handlebars.with(EscapingStrategy.JS).compile(source);
        } catch (final IOException e) {
            throw new IOError(e);
        }

        clientsToGenerate.forEach((type, executables) -> {
            try {
                final String jPackage = readPackage(type);
                final String className = type.getSimpleName().toString();
                final JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(jPackage + "." + className + "Stub");

                final ClientDefinition client = new ClientDefinition(jPackage, className, type.toString());

                final List<MethodDefinition> methods = executables.stream().map(method -> {
                    final String methodName = method.getSimpleName().toString();

                    final List<ArgumentDefinition> args = method.getParameters().stream().map(var -> new ArgumentDefinition(var.getSimpleName().toString(), var.asType().toString())).collect(Collectors.toList());
                    return new MethodDefinition(methodName, method.getReturnType().toString(), method.getReturnType().getKind() == TypeKind.VOID, args);
                }).collect(Collectors.toList());

                final Context context = Context.newBuilder(template).combine("client", client).combine("methods", methods).resolver(JavaBeanValueResolver.INSTANCE, MapValueResolver.INSTANCE, FieldValueResolver.INSTANCE).build();
                final String stubSource = template.apply(context);
                System.out.println(stubSource);

                builderFile.openWriter().append(stubSource).close();
            } catch (final Exception e) {
                e.printStackTrace();
                processingEnv.getMessager().printMessage(Kind.ERROR, "Unable to generate factory for " + type);
            }
        });

        return true;
    }

    private Type toJavaType(TypeMirror type) {
        outType(type.getClass());
        if (type instanceof WildcardType) {
        }

        return Object.class;
    }

    private void outType(Class<?> class1) {
        if (Object.class.equals(class1) || class1 == null) {
            return;
        }
        System.out.println(class1);
        outType(class1.getSuperclass());
        Arrays.stream(class1.getInterfaces()).forEach(this::outType);
    }

    private String readPackage(Element type) {
        if (type.getKind() == ElementKind.PACKAGE) {
            return type.toString();
        }

        if (type.getKind() == ElementKind.CLASS || type.getKind() == ElementKind.INTERFACE) {
            return readPackage(type.getEnclosingElement());
        }

        return null;
    }
}
